﻿using Meow.Core.Input;
using Meow.Core.Utils;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;

namespace Meow.Core;


/// <summary>
/// Meow Engine --- App need an engine instance to meowmeow
/// </summary>
public class Engine : Game
{
    public readonly string Version = "2023_12_05a1";
    public readonly string LogoPath = "CoreAssets/Img/icon";
    public readonly string SettingsPath = "settings.json";
    public MainSettings mainSettings { get; protected set; }
    protected Texture2D engineLogo;

    protected GraphicsDeviceManager _graphicsDeviceManager;
    JobScheduler.JobScheduler jobScheduler;


    public enum RunStatus
    {
        Error = -1,
        Success = 0,
        Running = int.MaxValue
    }
    protected RunStatus state;

    // tick: Runs independently on the background thread
    protected Thread _tickThread;
    public readonly Stopwatch Stopwatch = Stopwatch.StartNew();

    const int LogicInterval = 40;
    const int MaxLogicTicksBehind = 250;
    protected bool CanTick => state == RunStatus.Running;
    public long RunTime => Stopwatch.ElapsedMilliseconds;
    public int LocalTick { get; protected set; }

    /// <summary>
    /// should be set by TickThread only
    /// </summary>
    bool tickThreadWaiting = false;

    /// <summary>
    /// should be set by MainThread only
    /// </summary>
    int tickThreadShouldPause = 0;

    // update and render -----------------
    protected int _totalFrames;
    protected float _elapsedTime;
    protected int _FPS = 0;
    public int FPS => _FPS;

    public Color ClearColor = Color.CornflowerBlue;

    /// <summary>
    /// if true, next drawcall will directly return
    /// </summary>
    bool pauseDraw = false;

    /// <summary>
    /// only should set by draw call
    /// </summary>
    bool drawing = false;

    internal Dictionary<string, Scene> ScenesHandling = new Dictionary<string, Scene>();
    List<(string, Scene)> scenesToAdd = new List<(string, Scene)>();

    public void AddSceneToHandle(in string name, Scene scene) {
        scenesToAdd.Add((name, scene)); 
    }

    /// <summary>
    /// Load Settings from path, will call by constructor
    /// </summary>
    protected virtual bool LoadSettings(in string path)
    {
        mainSettings = new MainSettings();

        if (!File.Exists(path))
        {
            mainSettings.Save(path);
            return false;
        }
        else
        {
            JObject settings = JObject.Parse(File.ReadAllText(path));
            mainSettings = MainSettings.FromJson(settings);
            return true;
        }
    }

    public virtual void SaveCurrentSettings(in string path)
    {
        mainSettings.Save(path);
    }

    bool newSettings;
    public Engine(string title, string assetDirectory)
    {
        newSettings = !LoadSettings(SettingsPath);

        if (mainSettings == null)
            throw new Exception("Main Settings unavailable");

        _graphicsDeviceManager = new GraphicsDeviceManager(this);
        _graphicsDeviceManager.GraphicsProfile = GraphicsProfile.HiDef;

        Window.Title = title;
        Content.RootDirectory = assetDirectory;
        IsMouseVisible = true;

        Application.Engine = this;
        Application.Content = Content;
        Application.GraphicsDeviceManager = _graphicsDeviceManager;

        jobScheduler = new JobScheduler.JobScheduler("WorkerThread", Math.Max(Environment.ProcessorCount - 2, 1));
    }

    protected override void Initialize()
    {
        _tickThread = new Thread(RunTickThread);
        _tickThread.IsBackground = true;

        state = RunStatus.Running;

        #region apply settings
        // set resolution
        if (newSettings)
        {
            Screen.SetBestResolution(false);
            mainSettings.FullScreen = false;
            mainSettings.WindowWidth = Screen.Width;
            mainSettings.WindowHeight = Screen.Height;
        }
        else
            Screen.SetResolution(mainSettings.WindowWidth, mainSettings.WindowHeight, mainSettings.FullScreen);


        Application.GraphicsDeviceManager.SynchronizeWithVerticalRetrace = mainSettings.SynchronizeWithVerticalRetrace;

        // render settings
        if (mainSettings.MSAA > 0)
        {
            mainSettings.MSAA = mainSettings.MSAA > 8 ? 8 : mainSettings.MSAA;
            Application.GraphicsDeviceManager.PreferMultiSampling = true;
            // The number determines how good our antialiasing works. 
            // Possible values are 2,4,8,16,32, but not all work on all computers.
            // 4 is safe, and 8 is too in almost all cases
            // Higher numbers mean lower framerates
            Application.GraphicsDevice.PresentationParameters.MultiSampleCount = mainSettings.MSAA;
        }
        else
            mainSettings.MSAA = 0;

        Application.GraphicsDeviceManager.ApplyChanges();
        #endregion

        base.Initialize();
    }

    protected override void LoadContent()
    {
        base.LoadContent();


        // draw a logo on console
        engineLogo = Content.Load<Texture2D>(LogoPath);
        Console.WriteLine("Meow Engine Dev Version: " + Version + "\n");
        Console.Write(ImgTools.ImageToSymbol(engineLogo));
    }

    protected override void BeginRun()
    {
        InputHandler.Initialize();

        _tickThread.Start();
    }

    protected override void Update(GameTime gameTime)
    {
        InputHandler.Update(gameTime);

        _elapsedTime += (float)gameTime.ElapsedGameTime.TotalMilliseconds;

        if (_elapsedTime > 1000.0f)
        {
            _FPS = _totalFrames;
            _totalFrames = 0;
            _elapsedTime = 0;
        }

        var listOfUnloadScene = new List<string>();
        // update the scenes
        foreach (var (name, scene) in ScenesHandling)
        {
            if (scene.ToUnload)
                listOfUnloadScene.Add(name);
            else
                scene?.Update(gameTime);
        }

        base.Update(gameTime);

        InputHandler.AfterUpdate(gameTime);


        // update ScenesHandling collection
        if (listOfUnloadScene.Any() || scenesToAdd.Any())
        {
            pauseDraw = true; // stop draw
            tickThreadShouldPause = 10; // pause tick thread, tick thread will continue loop sleep(10ms) and then check if tickThreadShouldPause become 0

            while (!tickThreadWaiting || drawing) ; // wait for tick thread 
        }


        foreach (var (name, scene) in scenesToAdd)
        {
            ScenesHandling.Add(name, scene);
            scene.Initialize();
        }
        scenesToAdd.Clear();


        foreach (var sceneName in listOfUnloadScene)
        {
            if (ScenesHandling.TryGetValue(sceneName, out Scene scene))
            {
                ScenesHandling.Remove(sceneName);
                scene.Dispose();
            }
        }


        pauseDraw = false; // continue draw
        tickThreadShouldPause = 0; // continue tick thread
    }

    protected virtual void RunTickThread()
    {
        var nextLogic = RunTime;

        while (CanTick)
        {
            if (tickThreadShouldPause > 0)
            {
                tickThreadWaiting = true;
                Thread.Sleep(tickThreadShouldPause);
                continue;
            }
            else
                tickThreadWaiting = false;

            var now = RunTime;

            // If the logic has fallen behind too much, skip it and catch up
            if (now - nextLogic > MaxLogicTicksBehind)
                nextLogic = now;

            if (now >= nextLogic)
            {
                nextLogic += LogicInterval;
                LocalTick++;

                // tick the scene
                foreach (var (name, scene) in ScenesHandling)
                {
                    if (!scene.ToUnload)
                        scene?.Tick(LocalTick);
                }
            }
            else
                Thread.Sleep((int)(nextLogic - now));
        }

        // end tick thread
        Console.WriteLine("Tick thread end");
    }

    protected override void Draw(GameTime gameTime)
    {
        if (!pauseDraw)
        {
            drawing = true;

            _totalFrames++;

            GraphicsDevice.Clear(ClearColor);

            foreach (var (name, scene) in ScenesHandling)
            {
                if (!scene.ToUnload)
                    scene?.Render(_graphicsDeviceManager, gameTime, LocalTick);
            }
        }

        base.Draw(gameTime);
    }

    protected override void EndDraw()
    {
        if (!pauseDraw)
        {
        }

        base.EndDraw();

        drawing = false;
    }

    protected override void EndRun()
    {
        state = RunStatus.Success;

        jobScheduler?.Dispose();

        SaveCurrentSettings(SettingsPath);

        while (_tickThread.IsAlive) ;

        base.EndRun();
    }
}
